#pragma once
#include "ScenePrecompiled.hxx"

namespace Eifersucht
{

	/// Prototypes
	// Represents scene child transformation
	struct TransformationBase; TinyTypeDecl(TransformationBase);

	/// Implementation

	/*

	Maya : ThisWorld = ParentWorld *							// parent tranformation
					* T *										// apply translation
					* Roff * Rp * Rpre * R * Rpost * Rp-1 *		// apply rotation (the r-order is important!)
					* Soff * Sp * S * Sp-1						// apply scaling

	Max  : ThisWorld = ParentWorld *							// parent tranformation
					* T * R * S *								// apply local tranformation
					* GT * GR * GS								// apply geometric tranformation 
					(g-.. => parent-independent object-offset properties)

	*/

	typedef struct TransformationBase // Represents scene child transformation
	{
		enum class RotationOrder
		{
			EulerXYZ,
			EulerXZY,
			EulerYZX,
			EulerYXZ,
			EulerZXY,
			EulerZYX,
			SphericXYZ
		};

		struct PropertyCache; TinyTypeDecl(PropertyCache); struct PropertyCache
		{
			struct ChannelCache; TinyTypeDecl(ChannelCache); struct ChannelCache
			{
				// Timeline
				struct CurveCache; TinyTypeDecl(CurveCache); struct CurveCache
				{
					// Curve knot.
					struct Joint; TinyTypeDecl(Joint); struct Joint
					{
						Reign::Vector2 Point; // Time & value
						Reign::Vector2 Leaves; // Right
						Reign::Vector2 Meets; // Left
						CurveCachePtr Ref;
						JointPtr PrevPtr;
						JointPtr NextPtr;

						// The type of interpolation that is used on the curve segment, 
						// that starts from this curve knot.
						enum Interpolation 
						{
							Const,
							Linear, 
							Cubic,
						} eType;

						_Decl_value_prop_finl1(Point.y, float32, Value);
						_Decl_value_prop_finl1(Point.x, float32, Time);

						/*Reign::Vector2 Weights; // Left & right
						Reign::Vector2 Slopes; // Left & right
						_Decl_value_prop_finl1(Slopes.y, float32, RightSlope);
						_Decl_value_prop_finl1(Slopes.x, float32, LeftSlope);
						_Decl_value_prop_finl1(Weights.y, float32, RightWeight);
						_Decl_value_prop_finl1(Weights.x, float32, LeftWeight);*/

						Joint( _No_args_ );
						~Joint( _No_args_ );

						// Note: Requires the knots to be connected.
						// See: CurveCache::Reconnect().
						static void UpdateBoth(
							_Inout_ JointPtr knot, 
							_In_ Reign::Vector2 const &weights, 
							_In_ Reign::Vector2 const &slopes
							);

						// Note: Requires the knots to be connected.
						// See: CurveCache::Reconnect().
						static void UpdateLeft( 
							_Inout_ JointPtr knot, 
							_In_ float32 weight, 
							_In_ float32 slope
							);

						// Note: Requires the knots to be connected.
						// See: CurveCache::Reconnect().
						static void UpdateRight( 
							_Inout_ JointPtr knot, 
							_In_ float32 weight, 
							_In_ float32 slope
							);

						// Solve for real x => x^3 + a*x^2 + b*x + c = 0
						// Note: Return false, if real roots were not found.
						static bool SolveCubicInRadicals(
							_In_ float32 const a,
							_In_ float32 const b,
							_In_ float32 const c,
							_In_ float32 &x
							);
						
						// Interpolate with tangeants.
						static Reign::Vector2 CubicHermite( 
							_In_ JointPtr const start, 
							_In_ JointPtr const end,
							_In_ float32 const t
							);
					};

					typedef std::vector<JointPtr> JointCollection; TinyTypeDecl(JointCollection);
					JointCollection Knots;

					/*
					Note: These two methods can return a nullptr in one case. For the first function, the provided time 
					is on the left side of the curve timeline. For the second one, on the right.
					*/

					/* 
					Find the knot the curve segment with the provided time starts from. 
					*/
					JointPtr FindStartingKnot( _In_ float32 time, _In_ JointPtr start = nullptr );

					/* 
					Find the knot the curve segment with the provided time ends with. 
					*/
					JointPtr FindEndingKnot( _In_ float32 time, _In_ JointPtr end = nullptr );

					/* 
					Estimated (the more the curve segment looks like a small linear segment  the less the error is). 
					Note: If it fails to find the exact sln, it returns the estimated one.
					*/
					float32 CalculateEst( _In_ float32 const time, _In_opt_ JointPtr const hint = nullptr );

					/* 
					NOT IMPLEMENTED: Exact (a bit slower). 
					*/
					float32 Calculate( _In_ float32 const time, _In_opt_ JointPtr const hint = nullptr );

					/* 
					Ensures all the curve knots are connected correctly. 
					*/
					void ReconnectKnots( _No_args_ );

					CurveCache( _No_args_ );
					~CurveCache( _No_args_ );

				};

				// HighPart => TakeId, LowPart => TakeLayerId
				//typedef ULARGE_INTEGER TakeLayerPair;

				typedef struct TakeLayerPair
				{
					Reign::Neon::uint32x2_t Pair;
					_Decl_value_prop_finl1(Pair.n64_u32[1], uint32_t, TakeLayerId);
					_Decl_value_prop_finl1(Pair.n64_u32[0], uint32_t, TakeId);

					inline TakeLayerPair( _No_args_ ) { ZeroValue(&Pair); }
					inline TakeLayerPair( _In_ uint32_t l ) { TakeId(0); TakeLayerId(l); }
					inline TakeLayerPair( _In_ uint32_t t, _In_ uint32_t l ) { TakeId(t); TakeLayerId(l); }
					inline TakeLayerPair(TakeLayerPair const &other) { memcpy(this, &other, sizeof TakeLayerPair); }

					inline void operator=(int64_t const &l) { Pair.n64_i64[0] = l; }
					inline void operator=(uint64_t const &l) { Pair.n64_u64[0] = l; }
					inline void operator=(int32_t const &l) { TakeId(0); TakeLayerId(l); }
					inline void operator=(uint32_t const &l) { TakeId(0); TakeLayerId(l); }
					inline void operator=(TakeLayerPair const &other) { memcpy(this, &other, sizeof TakeLayerPair); }

					inline bool operator==(TakeLayerPair const &other) const { return Pair.n64_u64[0] == other.Pair.n64_u64[0]; }
					inline bool operator!=(TakeLayerPair const &other) const { return Pair.n64_u64[0] != other.Pair.n64_u64[0]; }
					inline bool operator<=(TakeLayerPair const &other) const { return Pair.n64_u64[0] <= other.Pair.n64_u64[0]; }
					inline bool operator>=(TakeLayerPair const &other) const { return Pair.n64_u64[0] >= other.Pair.n64_u64[0]; }
					inline bool operator<(TakeLayerPair const &other) const { return Pair.n64_u64[0] < other.Pair.n64_u64[0]; }
					inline bool operator>(TakeLayerPair const &other) const { return Pair.n64_u64[0] > other.Pair.n64_u64[0]; }
				} PairU64;

				// Dictionary { Animation layer id <=> Property animation curve cache }
				typedef std::map<TakeLayerPair, CurveCachePtr> LayeredCurveCollection; 
				TinyTypeDecl(LayeredCurveCollection); 

				LayeredCurveCollection Curves;

				ChannelCache( _No_args_ );
				~ChannelCache( _No_args_ );

			};

			typedef ChannelCache::PairU64 ChannelPairU64;

			enum Channel 
			{ 
				XChannel = 0, YChannel = XChannel + 1, ZChannel = YChannel + 1, WChannel = ZChannel + 1,
				RChannel = 0, GChannel = RChannel + 1, BChannel = GChannel + 1, AChannel = BChannel + 1,
				ChannelsXyz = 3, ChannelsXyzw = ChannelsXyz + 1,
				ChannelsRgb = 3, ChannelsRgba = ChannelsRgb + 1,
			};

			ChannelCachePtr Channels[Channel::ChannelsRgba];
			
			/*// Specialized function templates : 
			template <> REIGN_FORCEINLINING ChannelCachePtr Get<XChannel>() { return Channels[XChannel]; }
			template <> REIGN_FORCEINLINING ChannelCachePtr Get<RChannel>() { return Channels[XChannel]; }
			template <> REIGN_FORCEINLINING ChannelCachePtr Get<YChannel>() { return Channels[YChannel]; }
			template <> REIGN_FORCEINLINING ChannelCachePtr Get<GChannel>() { return Channels[YChannel]; }
			template <> REIGN_FORCEINLINING ChannelCachePtr Get<ZChannel>() { return Channels[ZChannel]; }
			template <> REIGN_FORCEINLINING ChannelCachePtr Get<BChannel>() { return Channels[ZChannel]; }
			template <> REIGN_FORCEINLINING ChannelCachePtr Get<AChannel>() { return Channels[AChannel]; } */

			template <Channel _Property_channel> REIGN_FORCEINLINING void Set( _In_ ChannelCachePtr channelCache ) 
			{ Channels[_Property_channel] = channelCache; }
			template <Channel _Property_channel> REIGN_FORCEINLINING ChannelCachePtr Get( _No_args_ ) 
			{ return Channels[_Property_channel]; }

			PropertyCache( _No_args_ );
			~PropertyCache( _No_args_ );

		};

		typedef PropertyCache::Channel PropertyChannel;
		typedef PropertyCache::ChannelPairU64 PropertyChannelPairU64;

		// Maya stuff :  

		/*
		Reign::Vector3 LclTranslationMax; // (Maya?)
		Reign::Vector3 LclTranslationMin; // (Maya?)
		Reign::Vector3 LclRotationOffset; // (Maya)
		Reign::Vector3 LclRotationPivot; // (Maya)
		Reign::Vector3 LclPostRotation; // (Maya)
		Reign::Vector3 LclPreRotation; // (Maya)
		Reign::Vector3 LclRotationMax; // (Maya?)
		Reign::Vector3 LclRotationMin; // (Maya?)
		Reign::Vector3 LclScalePivot; // (Maya)
		Reign::Vector3 LclScaleMax; // (Maya?)
		Reign::Vector3 LclScaleMin; // (Maya?)
		*/

		RotationOrder LclRotationOrder;

		Reign::Vector3 GeometricTranslation; // (Max)
		Reign::Vector3 GeometricRotation; // (Max)
		Reign::Vector3 GeometricScale; // (Max)
		Reign::Vector3 LclTranslation;			
		Reign::Vector3 LclRotation;			
		Reign::Vector3 LclScale;	

		PropertyCachePtr GeometricTranslationCache;
		PropertyCachePtr GeometricRotationCache;
		PropertyCachePtr GeometricScaleCache;
		PropertyCachePtr LclTranslationCache;
		PropertyCachePtr LclRotationCache;
		PropertyCachePtr LclScaleCache;

		TransformationBase();
		~TransformationBase();



	} XformBase; TinyTypeDecl(XformBase);

	typedef XformBase::PropertyChannelPairU64 PropertyChannelCurveId64;
	typedef XformBase::PropertyChannel XformPropertyChannel;
	TinyTypeDecl(PropertyChannelCurveId64);
	TinyTypeDecl(XformPropertyChannel);
};